import {typeList} from "../var/updateType";
import {TableBookList} from "./TableBookList";
import {TableArticleList} from "./TableArticleList";
import {updateFrequencyMap} from "../../renderer/var/constant";
import moment from "moment";
import {requestText} from "../function/httpFunc";
import {parseTonariYJObjToArticleList, parseXML, StXML} from "../function/xmlFunc";
import {ListManHuaDui} from "./MangaSite/ManHuaDui/ListManHuaDui";
import {MangaSite} from "./MangaSite/MangaSite";
import {ListDMZJ} from "./MangaSite/DMZJ/ListDMZJ";
export namespace BookUpdater {
    export interface BaseInfo {
        id: number;
        last_update_time: number;
        update_frequency: string;
        max_article_index: number;
        updateMethod: string;
        referURL: string;
        articleList: ArticleInfo[];
    }
    export interface ArticleInfo {
        articleIndex: number;
        articleName: string;
        download_method: MangaSite.TSiteId;
    }
    export interface UpdateResult {
        updated: boolean;
        updateTime: number;
        updateFrequency: string;
    }
    export interface SimpleArticleItem {
        article_index: number;
        book_id: number;
        download_method: MangaSite.TSiteId;
        localized: number;
        name: string;
        origin_url: string;
    }
}

export class BookUpdater {
    public readonly typeList = typeList;
    public readonly tableBookList = new TableBookList();
    public readonly tableArticleList = new TableArticleList();
    protected updateBookInfo: BookUpdater.BaseInfo = {
        id: 0,
        last_update_time: 0,
        update_frequency: "-",
        max_article_index: 0,
        articleList: [],
        updateMethod: "",
        referURL: "",
    };
    public async updateById(bookId: number): Promise<BookUpdater.UpdateResult> {
        const baseInfo = await this.readBookInfo(bookId);
        baseInfo.articleList = await this.getArticleListBy(bookId);
        this.updateBookInfo = baseInfo;
        return this.updateBook();
    }
    public updateBook(): Promise<BookUpdater.UpdateResult> {
        const baseInfo = this.updateBookInfo;
        if (!baseInfo.id) {
            return Promise.reject(new Error("book_id can be neither null nor 0"));
        }
        if (this.isUpdateTime()) {
            return this.startUpdate();
        }
        return Promise.resolve({
            updated: false,
            updateTime: baseInfo.last_update_time,
            updateFrequency: baseInfo.update_frequency,
        });
    }
    public isUpdateTime(): boolean {
        const baseInfo = this.updateBookInfo;
        if (
            baseInfo.last_update_time &&
            baseInfo.update_frequency &&
            baseInfo.update_frequency !== "-" &&
            updateFrequencyMap.hasOwnProperty(baseInfo.update_frequency)
        ) {
            const mUpdateTime = moment(baseInfo.last_update_time);
            let nextUpdateTime: any = 0;
            if (mUpdateTime.isValid()) {
                switch (baseInfo.update_frequency) {
                    case "day":
                        nextUpdateTime = mUpdateTime.add(1, "day");
                        break;
                    case "week":
                        nextUpdateTime = mUpdateTime.add(7, "day");
                        break;
                    case "month":
                        nextUpdateTime = mUpdateTime.add(1, "month");
                        break;
                }
                if (nextUpdateTime) {
                    return nextUpdateTime.startOf("day").valueOf() < moment().startOf("day").valueOf();
                }
            }
        }
        return true;
    }
    protected startUpdate(): Promise<BookUpdater.UpdateResult> {
        const baseInfo = this.updateBookInfo;
        if (!baseInfo.updateMethod || !this.typeList.find((item) => item === baseInfo.updateMethod)) {
            return Promise.reject(new Error(`[${baseInfo.updateMethod}] can not be found in type list;`));
        }
        if (!baseInfo.referURL) {
            return Promise.reject(new Error(`[${baseInfo.referURL}] is not a correct URL`));
        }
        switch (baseInfo.updateMethod) {
            case "rss-tonarinoyj":
                return this.updateBookByRSSTypeTonariYJ();
            case "ManHuaDui":
                return this.updateBookByManHuaDui();
            case "DMZJ":
                return this.updateBookByDMZJ();
            default:
                return Promise.reject(new Error(`wrong update type: [${baseInfo.updateMethod}]`));
        }
    }
    protected async updateBookByManHuaDui(): Promise<BookUpdater.UpdateResult> {
        const baseInfo = this.updateBookInfo;
        const updater = new ListManHuaDui(baseInfo);
        const newList = await updater.getNewList();
        return this.getLackList(newList);
    }
    protected async updateBookByDMZJ(): Promise<BookUpdater.UpdateResult> {
        const baseInfo = this.updateBookInfo;
        const updater = new ListDMZJ(baseInfo);
        const newList = await updater.getNewList();
        return this.getLackList(newList);
    }
    protected async updateBookByRSSTypeTonariYJ(): Promise<BookUpdater.UpdateResult> {
        const baseInfo = this.updateBookInfo;
        const articleList = await this.readRSSText(baseInfo.referURL, "tonariYJ");
        return this.getLackList(articleList as BookUpdater.SimpleArticleItem[]);
    }
    protected  getLackList(articleList: BookUpdater.SimpleArticleItem[]) {
        const baseInfo = this.updateBookInfo;
        const needAddList: BookUpdater.SimpleArticleItem[] = [];
        articleList.forEach((newItem) => {
            const foundItem = baseInfo.articleList.find((databaseItem) => {
                return (
                    databaseItem.articleIndex === newItem.article_index &&
                    databaseItem.download_method === newItem.download_method
                );
            });
            if (!foundItem) {
                needAddList.push(newItem);
            }
        });
        return this.addArticleList(needAddList);
    }
    protected addArticleList(list: BookUpdater.SimpleArticleItem[]): Promise<BookUpdater.UpdateResult> {
        const table =  this.tableArticleList;
        const baseInfo = this.updateBookInfo;
        return new Promise<BookUpdater.UpdateResult>((resolve, reject) => {
            table
                .insertByList(list as any)
                .then(() => {
                    const result: BookUpdater.UpdateResult = {
                        updated: true,
                        updateTime: moment().valueOf(),
                        updateFrequency: baseInfo.update_frequency,
                    };
                    resolve(result);
                })
                .catch((err) => {
                    console.error(err);
                    reject(err);
                });
        });
    }
    protected async readRSSText(url: string, rssType: string = "tonariYJ") {
        const rssTxt = await requestText(url);
        const obj = parseXML(rssTxt.replace(/<\?xml[^?]+\?>/, ""));
        switch (rssType) {
           case "tonariYJ":
               return this.parseTonariYJObj(obj);
            case "":
                return [];
        }
    }
    protected parseTonariYJObj(obj: StXML.BaseNode) {
        const itemList = parseTonariYJObjToArticleList(obj);
        itemList.sort((a, b) => {
            return a.pubDate - b.pubDate;
        });
        const articleList: Array<Partial<TableArticleList.Data>> = [];
        itemList.map((item, index) => {
            const articleItem: Partial<TableArticleList.Data> = {};
            articleItem.article_index = (index + 1);
            articleItem.name = item.title;
            articleItem.origin_url = item.link;
            articleItem.localized = 0;
            articleItem.download_method = "tonariYJ";
            articleItem.book_id = this.updateBookInfo.id;
            articleList.push(articleItem);
        });
        return articleList;
    }
    protected readBookInfo(bookId: number): Promise<BookUpdater.BaseInfo> {
        return this.tableBookList
            .select([
                "id",
                "last_update_time",
                "update_frequency",
                "max_article_index",
                "update_method",
                "update_refer_url"
            ])
            .where("id", bookId)
            .find()
            .then((rowInfo) => {
                return {
                    id: rowInfo.id,
                    last_update_time: rowInfo.last_update_time || 0,
                    update_frequency: rowInfo.update_frequency || "-",
                    max_article_index: rowInfo.max_article_index || 0,
                    articleList: [],
                    updateMethod: rowInfo.update_method,
                    referURL: rowInfo.update_refer_url,
                };
            });
    }
    protected getArticleListBy(bookId: number): Promise<BookUpdater.ArticleInfo[]>  {
        return this.tableArticleList
            .select([
                "article_index",
                "name",
                "download_method"
            ])
            .where("book_id", bookId)
            .findAll()
            .then((rows) => {
                return rows.map((row) => {
                    return {
                        articleIndex: row.article_index,
                        articleName: row.name,
                        download_method: row.download_method,
                    };
                });
            });
    }
}
